import React, { useRef, useState } from "react";
import SparkMD5 from "spark-md5";
import axios, { CancelTokenSource } from "axios";
import { Button, Input } from "antd";

// 切片大小
const CHUNK_SIZE = 2 * 1024 * 1024;
const MAX_CONCURRENCY = 3;

function sliceFile(file: File, chunkSize = CHUNK_SIZE) {
  // 用于存储文件切片的数组
  const chunks = [];
  // 当前切片的起始位置
  let cur = 0;
  // 当当前位置小于文件大小时,继续切片
  while (cur < file.size) {
    // 从当前位置开始,截取指定大小的文件切片,并添加到数组中
    chunks.push(file.slice(cur, cur + chunkSize));
    // 更新当前位置,指向下一个切片的起始位置
    cur += chunkSize;
  }
  // 返回包含所有文件切片的数组
  return chunks;
}

function getFileHash(file: File): Promise<string> {
  return new Promise((resolve, reject) => {
    // 创建实例,用于计算文件的MD5哈希值
    const spark = new SparkMD5.ArrayBuffer();
    //创建实例 读取文件内容
    const reader = new FileReader();
    // 使用全局常量CHUNK_SIZE作为文件切片的大小
    const chunkSize = CHUNK_SIZE;
    // 调用sliceFIle函数将文件切割成多个切片
    const chunks = sliceFile(file, chunkSize);
    // 记录当前正在处理的切片索引,设置初始值为0
    let current = 0;
    // 读取下一个切片的内容,并将其添加到SparkMD5实例中
    function loadNext() {
      // 当前切片索引大于等于切片数组的长度时,使用切片都已处理完成,计算并返回最终的MD5哈希值
      if (current >= chunks.length) {
        resolve(spark.end());
        return;
      }
      // 使用FileReader读取当前切片的内容,并将其添加到SparkMD5实例中

      reader.readAsArrayBuffer(chunks[current]);
    }

    reader.onload = (e) => {
      spark.append(e.target!.result as ArrayBuffer);
      current++;
      loadNext();
    };
    reader.onerror = reject;
    loadNext();
  });
}

const concurrentRequest = async (tasks: (() => Promise<any>)[], max = MAX_CONCURRENCY) => {
  let i = 0;
  let active = 0;
  const results: any[] = [];
  return new Promise((resolve, reject) => {
    function next() {
      if (i === tasks.length && active === 0) return resolve(results);
      while (active < max && i < tasks.length) {
        const cur = i++;
        active++;
        tasks[cur]()
          .then((res) => (results[cur] = res))
          .catch(reject)
          .finally(() => {
            active--;
            next();
          });
      }
    }
    next();
  });
};

export default function FileUploader() {
  const [progress, setProgress] = useState(0);
  const [status, setStatus] = useState<"idle" | "uploading" | "paused" | "done" | "error">("idle");
  const [file, setFile] = useState<File | null>(null);
  const cancelTokens = useRef<CancelTokenSource[]>([]);
  const uploadedChunks = useRef<Set<number>>(new Set());
  const fileHashRef = useRef<string>("");

  // 检查文件状态
  async function checkFile(fileHash: string, filename: string) {
    // 走接口
    const res = await axios.post("http://localhost:3000/upload/check", { fileHash, filename });
    return res.data;
  }

  // 上传切片
  async function uploadChunks(file: File, fileHash: string, uploaded: number[]) {
    const chunks = sliceFile(file, CHUNK_SIZE);
    const total = chunks.length;
    let finished = uploaded.length;
    setProgress(Math.floor((finished / total) * 100));
    uploadedChunks.current = new Set(uploaded);

    const tasks = chunks.map((chunk, idx) => {
      if (uploadedChunks.current.has(idx)) return () => Promise.resolve("skip");
      return () => {
        const form = new FormData();
        form.append("chunk", chunk);
        form.append("fileHash", fileHash);
        form.append("chunkIndex", idx.toString());
        form.append("filename", file.name);
        const source = axios.CancelToken.source();
        cancelTokens.current[idx] = source;
        return axios
          .post("http://localhost:3000/upload/chunk", form, {
            cancelToken: source.token,
          })
          .then((res) => {
            finished++;
            setProgress(Math.floor((finished / total) * 100));
            return res;
          });
      };
    });

    await concurrentRequest(tasks, MAX_CONCURRENCY);
    // 合并
    await axios.post("http://localhost:3000/upload/merge", {
      fileHash,
      filename: file.name,
      chunkSize: CHUNK_SIZE,
    });
    setStatus("done");
    setProgress(100);
  }

  // 主上传流程
  async function upload(file: File) {
    setStatus("uploading");
    const fileHash = await getFileHash(file);
    fileHashRef.current = fileHash;
    const check = await checkFile(fileHash, file.name);

    // 秒传
    if (check.uploaded) {
      setStatus("done");
      setProgress(100);
      return;
    }
    // 断点续传
    const uploaded = (check.uploadedChunks || []).map(Number);
    try {
      await uploadChunks(file, fileHash, uploaded);
    } catch (e) {
      setStatus("error");
    }
  }

  function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
    const f = e.target.files?.[0];
    if (f) {
      setFile(f);
      setProgress(0);
      setStatus("idle");
    }
  }
// 开始上传
  function handleStart() {
    if (file) upload(file);
  }
// 暂停
  function handlePause() {
    setStatus("paused");
    cancelTokens.current.forEach((token) => token && token.cancel("paused"));
  }
// 继续
  // function handleResume() {
  //   if (file) upload(file);
  // }

  return (
    <div>
      <Input type="file" onChange={handleFileChange} style={{width:"160px"}}/>
      <div>
        <Button onClick={handleStart} disabled={!file || status === "uploading"}>开始上传</Button>
        <Button onClick={handlePause} disabled={status !== "uploading"}>暂停上传</Button>
        {/* <Button onClick={handleResume} disabled={status !== "paused"}>继续上传</Button> */}
      </div>
      
      <div>进度: {progress}% 状态: {status}</div>
    </div>
  );
}